#include <sys/time.h>
#include <stdint.h>
#include <iostream>
#include "StatCalc.h"
#include "BinomialStat.h"
using namespace std;

static double getTimeMS(void) {
    struct timeval time ;
    gettimeofday(&time, NULL) ;
    return time.tv_sec * 1000.0 + time.tv_usec / 1000.0 ;
}

static double getCpuSeconds() { return 1000.0 * (double)(clock()) / (double)(CLOCKS_PER_SEC); }

static uint arand()
{
    static uint seed = 123456;
    seed = 1664525UL*seed + 1013904223UL;
    return seed;
}

static inline uint hash1(uint x, uint a1, uint a2, uint a3, uint a4, uint a5, uint a6)
{
    x += x << a1;
    x ^= x >> a2;
    x += x << a3;
    x ^= x >> a4;
    x += x << a5;
    x ^= x >> a6;
    return x;
}

static uint hash2(uint x, uint a1, uint a2, uint a3, uint a4, uint a5, uint a6)
{
    x ^= x << a1;
    x ^= x >> a2;
    x ^= x << a3;
    x ^= x >> a4;
    x ^= x << a5;
    x ^= x >> a6;
    return x;
}

// BUSTED!
// NOTHING with mean < 1.0, var < 1.0
static uint hash3(uint x, uint a1, uint a2, uint a3, uint a4, uint a5, uint a6)
{
    x ^= (x << a1) ^ (x >> a2);
    x ^= (x << a3) ^ (x << a4);
    x ^= (x << a5) ^ (x >> a6);
    return x;
}

static uint hash4(uint x, uint a1, uint a2, uint a3, uint a4, uint a5, uint a6)
{
    x ^= (x << a1) ^ (x >> a2);
    x ^= (x << a3);
    x ^= (x << a4) ^ (x >> a5);
    x ^= (x >> a6);
    return x;
}

static uint hash5(uint x, uint a1, uint a2, uint a3, uint a4, uint a5, uint a6)
{
    x ^= (x << a1) ^ (x >> a2);
    x ^= (x << a3);
    x ^= (x << a4) ^ (x >> a5);
    x ^= (x << a6);
    return x;
}

static uint hash6(uint x, uint a1, uint a2, uint a3, uint a4, uint a5, uint a6)
{
    x ^= (x << a1) ^ (x >> a2);
    x ^= (x >> a3);
    x ^= (x << a4) ^ (x >> a5);
    x ^= (x << a6);
    return x;
}

static uint hash7(uint x, uint a1, uint a2, uint a3, uint a4, uint a5, uint a6)
{
    x ^= (x << a1) ^ (x >> a2);
    x ^= (x >> a3);
    x ^= (x << a4) ^ (x >> a5);
    x ^= (x >> a6);
    return x;
}

static uint hash8(uint x, uint a1, uint a2, uint a3, uint a4, uint a5, uint a6)
{
    /* NONE!!
    x ^= (x << a1);
    x ^= (x << a2) & (x >> a3);
    x ^= (x >> a4);
    x ^= (x << a5) ^ (x >> a6);
    return x;
    */
    x ^= (x >> a1);
    x ^= (x << a2) & (x >> a3);
    x ^= (x << a4);
    x ^= (x << a5) ^ (x >> a6);
    return x;
}

static uint hash9(uint x, uint a1, uint a2, uint a3, uint a4, uint a5, uint a6)
{
    x ^= (x << a1) ^ (x >> a2);
    x += (x << a3);
    x ^= (x << a4) ^ (x >> a5);
    x += (x << a6);
    return x;
}


static inline uint bitcount(uint x)
{
#if 1
    x -=  (x>>1) & 0x55555555;
    x  = ((x>>2) & 0x33333333) + (x & 0x33333333);
    x  = ((x>>4) + x) & 0x0f0f0f0f;
    x *= 0x01010101;
    return  x>>24;
#else
    return __builtin_popcount(x);
#endif

}

#define HASH hash1
static bool hashcount(int a1, uint a2, uint a3, uint a4, uint a5, uint a6,
                      BinomialStat& stats)
{
    const uint imax = 64;

    stats.clear();

    for (uint j = 0; j < 32; ++j) {
        for (uint i = 0; i < imax; ++i) {
            uint a = arand();
            uint ahash = HASH(a, a1,a2,a3,a4,a5,a6);
            uint b = a ^ (1 << j);
            uint bhash = HASH(b, a1,a2,a3,a4,a5,a6);
            //stats += bitcount(ahash ^ bhash);
            stats.insert(j, bitcount(ahash ^ bhash));
        }
        if ((fabs(stats.mean(j) - 16.0) > 0.5)
            || (fabs(stats.variance(j) - 8.0) > 0.5)
            || stats.rms(j) > 0.3
            || stats.rms_max() > 0.03
            ) {
            return false;
        }
    }
    return true;
}

int main()
{
    BinomialStat stats;
    cout << "{\n";
    double t0 = 0.0;
    double c0 = 0.0;
    for (uint a1 = 10; a1 < 32; ++a1) {
        for (uint a2 = 1; a2 < 32; ++a2) {
            for (uint a3 = 1; a3 < 32; ++a3) {
                t0 = getTimeMS();
                c0 = getCpuSeconds();

                cerr << "a1=" << a1 << ", a2=" << a2 << ", a3=" << a3;

                for (uint a4 = 1; a4 < 32; ++a4) {
                    for (uint a5 = 1; a5 < 32; ++a5) {
                        for (uint a6 = 1; a6 < 32; ++a6) {
                            if (hashcount(a1,a2,a3,a4,a5,a6, stats) &&
                                (fabs(stats.mean() -16.0) < 0.1) &&
                                (fabs(stats.variance() -8.0) < 0.5) &&
                                stats.rms() < 0.02 &&
                                stats.rms_max() < 0.04) {
                                cout << "{{" << a1 << ',' << a2 << ',' << a3 << ',' << a4 << ',' << a5 << ',' << a6 << "}, "
                                     << stats.mean() << ',' << stats.variance() << ',' << stats.rms() << ',' << stats.rms_max() << "}, " << endl;
                                cerr << "{{" << a1 << ',' << a2 << ',' << a3 << ',' << a4 << ',' << a5 << ',' << a6 << "}, "
                                     << stats.mean() << ',' << stats.variance() << ',' << stats.rms() << ',' << stats.rms_max() << "}, \n";
                            }
                        }
                    }
                }
                cerr << " -- "  << (getTimeMS() - t0) << ", " << (getCpuSeconds() - c0) << "\n";
            }
        }
    }
    cout << '}' << endl;
    return 0;
}
